package owl

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"sort"
	"strconv"
	"sync"
	"time"

	"gitee.com/JMArch/rpcmon-gateway/conf"
	"gitee.com/JMArch/rpcmon-gateway/log"
	"gitee.com/JMArch/rpcmon-gateway/storage"
	"gitee.com/JMArch/rpcmon-gateway/util"
	"go.uber.org/zap"
)

const (
	OVERVIEW  = "overview"
	NGINX     = "nginx"
	EXCEPTION = "exception"
)

type ClientGroup struct {
	rw    sync.RWMutex
	group map[string]*Client
}

type Client struct {
	log      *zap.Logger
	mu       sync.RWMutex
	wg       sync.WaitGroup
	cache    *storage.Cache
	lifeTime time.Duration
	interval time.Duration
	url      string
	name     string
	quit     chan bool
	timer    *time.Ticker
	menus    string
}

func NewClientGroup(cacheGroup *storage.CacheGroup, config *conf.GatewayConf, logger *log.GatewayLog) (*ClientGroup, error) {
	g := &ClientGroup{
		group: make(map[string]*Client, 0),
	}

	for name, cache := range cacheGroup.OWLGroup {
		c, err := New(cache, config, logger)
		if err != nil {
			return nil, err
		}
		g.group[name] = c
	}

	return g, nil
}

func (g *ClientGroup) Close() {
	for _, client := range g.group {
		client.Close()
	}
	return
}

func New(cache *storage.Cache, config *conf.GatewayConf, logger *log.GatewayLog) (*Client, error) {
	name := cache.Name

	b, ok := config.OWL.BackendMap[name]
	if !ok {
		return nil, errors.New("backend name not exists")
	}

	if b.History > config.OWL.MaxHistory {
		logger.Warn("history grreater than MaxHistory", zap.Duration("History", b.History), zap.Duration("MaxHistory", config.OWL.MaxHistory))
		b.History = config.OWL.MaxHistory
	}

	client := &Client{
		name:     name,
		log:      logger.Named("owl." + name),
		cache:    cache,
		lifeTime: b.History,
		interval: b.Interval,
		quit:     make(chan bool, 0),
		url:      "http://" + b.Addr + b.Path,
		menus:    config.OWL.Menus,
	}

	if client.name == OVERVIEW {
		client.timer = time.NewTicker(30 * time.Second)
		client.requestLoop()
		//} else if client.name == EXCEPTION {
		//	client.timer = time.NewTicker(60 * time.Second)
		//  client.requestLoop()
		//} else if client.name == NGINX {
		//	client.timer = time.NewTicker(60 * time.Second)
		//  client.requestLoop()
	}

	client.log.Info("owl client start", zap.String("type", name), zap.String("url", client.url), zap.Duration("lifetime", b.History), zap.Duration("interval", b.Interval))

	return client, nil
}

func (c *Client) requestLoop() {
	c.wg.Add(1)
	go func() {
		defer c.wg.Done()
		for {
			select {
			case <-c.quit:
				return
			case <-c.timer.C:
				if c.name == OVERVIEW {
					c.overview()
					//} else if c.name == EXCEPTION {
					//	c.getLog()
					//} else if c.name == NGINX {
					//	c.getLog()
				}
			}
		}
	}()

	return
}

func (c *Client) overview() {

	defer func() {
		if p := recover(); p != nil {
			c.log.Error("overview panic", zap.Any("error", p))
		}
	}()

	client := &http.Client{}
	request, err := http.NewRequest("GET", c.url, nil)
	if err != nil {
		c.log.Error("http new request fail", zap.Error(err))
		return
	}

	c.log.Debug("query overview", zap.String("url", c.url))

	request.Header.Add("Authorization", util.GetAuth())

	resp, err := client.Do(request)
	if err != nil {
		c.log.Error("http get fail", zap.String("url", c.url), zap.String("authKey", request.Header.Get("Authorization")), zap.Error(err))
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		c.log.Error("http get fail", zap.String("url", c.url), zap.String("authKey", request.Header.Get("Authorization")), zap.Int("code", resp.StatusCode))
		return
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		c.log.Error("http read fail", zap.String("url", c.url), zap.String("authKey", request.Header.Get("Authorization")), zap.Error(err))
		return
	}

	hmap := make(map[string]interface{}, 0)
	err = json.Unmarshal(body, &hmap)
	if err != nil {
		c.log.Error("http decode fail", zap.String("url", c.url), zap.String("authKey", request.Header.Get("Authorization")), zap.String("body", string(body)), zap.Error(err))
		return
	}

	if _, ok := hmap["data"]; ok {
		detail := hmap["data"].(map[string]interface{})
		except := detail["whole_exception_count"].(float64)
		total := detail["whole_query_count"].(float64)

		whole := make(storage.Node, 0)
		whole["Service"] = "_all_"
		whole["Name"] = "_all_"
		whole["Exception"] = int(except)
		whole["Query"] = int(total)

		allNodes := []storage.Node{whole}

		arr := detail["page_data"].([]interface{})
		for i, _ := range arr {
			item := arr[i].(map[string]interface{})
			id := strconv.Itoa(int(item["id"].(float64)))
			service := item["name"].(string)
			totalChange := item["totalTreand"].(float64)
			exceptionChange := item["excTreand"].(float64)
			curQuery := item["query_count"].(float64)
			curException := item["exception_count"].(float64)

			pnode := make(storage.Node, 0)
			pnode["Service"] = id
			pnode["Name"] = service
			pnode["totalChange"] = totalChange
			pnode["exceptionChange"] = exceptionChange
			pnode["CurQuery"] = curQuery
			pnode["CurException"] = curException

			//c.log.Debug("detail", zap.Any("name", pnode["Name"]), zap.Any("totalChange", item["totalTreand"]), zap.Any("exceptionChange", item["excTreand"]), zap.Any("totalChange1", pnode["totalChange"]), zap.Any("exceptionChange1", pnode["exceptionChange"]))

			bucket := make(map[int]storage.Node, 0)
			for _, o := range item["epm"].([]interface{}) {

				e := o.(map[string]interface{})
				n := int(e["Y"].(float64))
				ts := int64(e["x"].(float64))

				node := make(storage.Node, 0)
				node["Exception"] = n
				node["Create"] = time.Unix(ts/1000, (ts%1000)*1000000)
				bucket[int(ts)] = node
			}

			for _, o := range item["qpm"].([]interface{}) {
				e := o.(map[string]interface{})
				ts := int(e["x"].(float64))

				if node, ok := bucket[ts]; ok {
					node["Query"] = int(e["Y"].(float64))
				} else {
					c.log.Warn("qpm and epm mismatch", zap.Int("timestamp", ts), zap.Any("epm", item["epm"]), zap.Any("qpm", item["qpm"]))
				}
			}

			needSort := make([]int, 0)
			for ts, _ := range bucket {
				needSort = append(needSort, ts)
			}
			sort.Ints(needSort)

			var lastNode storage.Node
			list := make([]storage.Node, 0)
			for _, ts := range needSort {
				node, _ := bucket[ts]
				list = append(list, node)
				lastNode = node
			}
			pnode["List"] = list
			allNodes = append(allNodes, pnode)

			whole["Create"] = lastNode["Create"]
		}

		c.cache.Set("_all_", allNodes, whole["Create"].(time.Time), c.lifeTime, c.interval)
	} else {
		c.log.Error("overview fail", zap.Any("result", hmap))
	}
}

func (c *Client) Close() {
	if c.timer != nil {
		c.timer.Stop()
	}
	close(c.quit)
	c.wg.Wait()
	c.log.Debug("stop owl backend", zap.String("type", c.name))
}
